import ntpath
import os
import sys
import pandas as pd
import openpyxl as excel
import json 
from MyLogger import MyLogger
from MdbHelper import MdbHelper

class MainConsolidator(object):

    version = "1.1"
    columns:list

    def __init__(self,logFile,logStream) -> None:
        self.productions={}
        self.guids={}
        self.positions:pd.DataFrame = None
        self.mlog=MyLogger(__name__,log_file=logFile,outStream=logStream)
        self.input_data:pd.DataFrame=None
        

    def load_guids(self,guidsDir:str):
        """Загрузка справочников из csv

        Args:
            guidsDir (str): Директория с csv-файлами
        """
        try:
            self.mlog.info(f"Загрузка справочников из {guidsDir}")
            files = os.listdir(guidsDir)
            for file in files:
                fullName = f"{guidsDir}\\{file}"
                key = file.replace(".csv","")
                df = pd.read_csv(fullName,encoding="windows-1251",delimiter=";")
                self.guids[key]=df
                # self.mlog.info(f"Справочник '{key}' загружен")
        except: raise

    def load_guids_x(self,guidsDir:str):
        """Загрузка справочников из xlsx

        Args:
            guidsDir (str): Директория с xlsx-файлами
        """
        try:
            self.mlog.info(f"Загрузка справочников из {guidsDir}")
            files = [f for f in os.listdir(guidsDir) if "~$" not in f]
            for file in files:
                fullName = f"{guidsDir}\\{file}"
                key = file.replace(".xlsx","")
                df = pd.read_excel(fullName)
                self.guids[key]=df
                # self.mlog.info(f"Справочник '{key}' загружен")
        except: raise
    def load_input_data(self,filesList:list):
        """Загрузка входных данных

        Args:
            filesList (list): Список файлов
        """
        try:
            dfs=[]
            for file in filesList:
                try:
                    fname,fextension = os.path.splitext(file)
                    if str(fextension).lower()==".xlsx": dfs.append(self.__load_excel(file))
                    if str(fextension).lower()==".mdb": dfs.append(self.__load_mdb(file))                    
                except Exception as exp:
                    self.mlog.warning(f"Ошибка загрузки файла '{file}'",exc_info=True)
            if dfs.__len__()>0:
                    self.input_data=pd.concat(dfs)
                    self.mlog.info(f"Входные данные загружены: {self.input_data.shape[0]} строк, {self.input_data.shape[1]} столбцов")
                    for col in self.columns:
                        if col not in self.input_data.columns.to_list(): self.input_data[col]=pd.NA
                    self.input_data = self.input_data[self.columns]
                    self.input_data = self.input_data.apply(self.level1_handler,axis=1)
                    self.mlog.info("1 обработчик отработал")
                    self.input_data.fillna("|",inplace=True)
                    self.input_data.replace("|",pd.NA,inplace=True)
        except: raise

    ############# handlers ##########################################
    def level1_handler(self,rec:pd.Series)->pd.Series:
        try:
            # for itm in self.level1Config: rec[itm["column"]] = self.scalar_in_guid(itm["guid"],eval(itm["query"]),itm["quid_column"])            
            # 1. Оперделение производства
            prod2 = self.scalar_in_guid("Маппинг производств",f"`Производство`=='{rec['Производство']}'","Производство2")
            if pd.isna(prod2): prod2="-" # bug fix отстутствие атрибута 'производство'
            rec["Производство2"] = prod2
            # 2. Определение Группы марок стали по потребительским свойствам (ГМСПС)
            # Внимание! требуется проработка и корретировка
            rec["ГМСПС"] = self.scalar_in_guid("Группы марок стали по потребительским свойствам", f"`Марка стали`=='{rec['Марка стали <Производство>']}'","Группа марок")
            if pd.isna(rec['Марка стали <Производство>']) or str(rec['Марка стали <Производство>'])=="-": rec["ГМСПС"]="ГУ"

            # 3. Определение продукта и Признака БЗ по НТД
            ntd_list = rec[["НТД качества (прокат) <Производство>","НТД качества (трубы стальные) <Производство>","НТД качества (колеса) <Производство>"]].to_list()
            ntd = pd.NA
            for n in ntd_list: 
                if not pd.isna(n): ntd=n
            rec["НТД"]=ntd
            rec["Первичный продукт"] = self.scalar_in_guid("Продукт по НТД", f"`Производство`=='{prod2}' and `НТД качества (прокат) <Производство>`=='{ntd}'" ,"Продукт")
            if pd.isna(rec["Первичный продукт"]):
                rec["Первичный продукт"] = self.scalar_in_guid("Продукт по НТД", f"`Производство`=='{prod2}' and `НТД качества (прокат) <Производство>`=='*'" ,"Продукт")
            if "прокат рулонный о" in str(rec["Полное наименование материала"]).lower() or "прокат рулонный o" in str(rec["Полное наименование материала"]).lower(): rec["Первичный продукт"] = "ПОЛОСА"
            if prod2=="Трубы ТБД": rec["Первичный продукт"] = "ТР Д"
            if prod2=="Трубы ТБД" and rec["Диаметр, мм <Производство>"]=="813": rec["Первичный продукт"] = "ТРК Д"
            if prod2=="Трубы ТБД" and rec["Диаметр, мм <Производство>"]=="508": rec["Первичный продукт"] = "ТРК Д508"
            rec["Признак БЗ"] = self.scalar_in_guid("Продукт по НТД",f"`Производство`=='{prod2}' and `НТД качества (прокат) <Производство>`=='{ntd}'","Доп признак")

            # 4. Костыль для ТРУБЫ Д ГЮ
            if str(rec["Первичный продукт"])=="ТРУБА Д" and str(rec["ГМСПС"])=="ГЮ": rec["ГМСПС"]="ГУ"


            return rec
        except Exception as exp:
            self.mlog.warning(f"level1_handler [{rec['ID в SAP ERP VMZ']}] не обработано. ({exp})",exc_info=True)
            return rec
    #################################################################

    def debug_stub(self)->str:
        q= "`Производство`=='{rec['Производство']}'"
        return  f'f"{q}"'

    def __load_excel(self,fileName:str)->pd.DataFrame:
        try:
            dfs=[]
            short_name=ntpath.basename(fileName)
            # direct pandas version (work only with openpyxl==3.0.7)
            xl = pd.ExcelFile(fileName,engine="openpyxl")
            for sheet in  xl.sheet_names:
                try:
                    print(sheet)
                    df = pd.read_excel(fileName,sheet_name=sheet)
                    df["Файл"]=short_name
                    df["Лист"]=sheet
                    dfs.append(df)
                    self.mlog.info(f"[{short_name}].[{sheet}] загружен")
                except Exception as exp:
                    self.mlog.warning(f"Ошибка загрузки листа '{short_name}'[{sheet}]",exc_info=True)
            
            if dfs.__len__()==0: raise Exception("Файл не содержит данных")
            return pd.concat(dfs)
        except: raise
    
    def __load_mdb(self,fileName:str)->pd.DataFrame:
        try:
            # mdb = MdbHelper(fileName,autoconnect=True)
            # df:pd.DataFrame = mdb.get_table_df("Справочник МТР1")
            # mdb.close_connection()
            ## issue http://vsys01775:8282/flea/pyexcelcons3/-/issues/1
            mdb = MdbHelper(fileName)
            df:pd.DataFrame = mdb.get_mdbt_table_df()    
            if df.empty: raise Exception("Файл не содержит данных или отсутствует таблица [Справочник МТР1]")
            return df
        except: raise

    def dump_input_data(self,fileName:str="input_data.xlsx"):
        try:
            if self.input_data is not None: 
                cols = {"columns":self.input_data.columns.to_list()}
                with open(fileName.replace(".xlsx",".json"),mode="w") as fs:
                    json.dump(cols, fs, indent=2, sort_keys=False, ensure_ascii=False)
                    fs.flush()
                self.input_data.to_excel(fileName,index=False)
        except: raise

    def find_in_guid(self,guidName,query:str)->tuple:
        """Первая строка выборки из справочника контрагентов

        Args:
            guidName: Имя справочника \n
            query: Текст запроса

        Example of query:  f"`Дивизион`=='{row['Дивизион']}' and `Группа клиентов либо поставщиков`=='{row['Группа клиентов']}' and `Место поставки`=='{row['Место поставки']}'" \n
        
        Returns:
            _type_: кортеж (первая строка, реальное количество строк)
        """        
        try:

            gdf:pd.DataFrame = self.guids.get(guidName,None)
            if gdf is None: raise Exception(f"Справочника '{guidName}' не существует")
            res = gdf.query(query)
            if res.empty: return tuple([None,0])
            return tuple([res.iloc[0],res.shape[0]])
        except: raise       

    def find_in_guid2(self,guidName,query:str,columnList:list)->pd.Series:
        """Первая строка выборки из справочника контрагентов

        Args:
            guidName: Имя справочника \n
            query: Текст запроса

        Example of query:  f"`Дивизион`=='{row['Дивизион']}' and `Группа клиентов либо поставщиков`=='{row['Группа клиентов']}' and `Место поставки`=='{row['Место поставки']}'" \n
        
        Returns:
            _type_: кортеж (первая строка, реальное количество строк)
        """        
        try:
            gdf:pd.DataFrame = self.guids.get(guidName,None)
            if gdf is None: raise Exception(f"Справочника '{guidName}' не существует")
            res = gdf.query(query)
            if res.empty:
                data_={}
                for col in columnList: data_[col]=pd.NA 
                return pd.Series(data=data_)
            return res.iloc[0][columnList]
        except: raise

    def scalar_in_guid(self,guidName,query:str,column:str):
        """Первая строка выборки из справочника контрагентов (единичное значение)

        Args:
            guidName: Имя справочника \n
            query: Текст запроса

        Example of query:  f"`Дивизион`=='{row['Дивизион']}' and `Группа клиентов либо поставщиков`=='{row['Группа клиентов']}' and `Место поставки`=='{row['Место поставки']}'" \n
        
        Returns:
            _type_: Значение
        """        
        try:
            gdf:pd.DataFrame = self.guids.get(guidName,None)
            if gdf is None: raise Exception(f"Справочника '{guidName}' не существует")
            res = gdf.query(query)
            if res.empty: 
                return pd.NA
            return res.iloc[0][column]
        except: raise

    def float_converter(self,row:pd.Series,colName:str)->pd.Series:
        try:
            row[colName]=float(str(row[colName]).replace(",","."))
            return row
        except:
            row[colName]=0
            return row

    def nearest_in_guid(self,guidName:str,query:str,nearestColumn:str,nearestValue:float,valueColumn:str):
        """Получить ближайшее значение из справочника 

        Args:
            guidName: Имя справочника \n
            query: Текст запроса \n
            nearestColumn (str): Имя колонки приближаемых значений \n
            nearestValue (float): Приближаемое входное значение \n
            valueColumn (str): Имя колонки с результатом \n

        Raises:
            Exception: _description_

        Returns:
            _type_: _description_
        """
        try:
            gdf:pd.DataFrame = self.guids.get(guidName,None)
            if gdf is None: raise Exception(f"Справочника '{guidName}' не существует")
            df = gdf.query(query)
            if df.empty: return pd.NA
            df = df.apply(lambda row: self.float_converter(row,nearestColumn),axis=1)
            df_res = df.iloc[(df[nearestColumn]-nearestValue).abs().argsort()[:1]]
            return df_res[valueColumn].values[0]
        except: raise

    ###############    General methods    #########################

    def strength_by_steel(self,steel_mark:str):
        """П0110_Кл_прочности_по_марке_стали

        Args:
            steel_mark (str): Марка стали
        """
        try:
            return self.scalar_in_guid("Класс прочности по марке стали",f"`Марка стали`=='{steel_mark}'","Класс прочности")
        except: raise

    def strength_mapping(self,strength_class:str,steel_mark:str):
        """П0120_Маппинг_Кл_прочности
        Args:
            strength_class (str): _description_
            steel_mark (str): _description_
        """
        try:
            
            strength_class_ = self.scalar_in_guid("Маппинг класса прочности",f"`ИсX Класс прочности`=='{str(strength_class)}'","Результ Класс прочности")
            steel_mark_     = self.scalar_in_guid("Маппинг класса прочности",f"`ИсX Класс прочности`=='{str(steel_mark)}'","Результ Класс прочности")
            if pd.isna(strength_class_): strength_class_=strength_class
            if pd.isna(steel_mark_): steel_mark_=steel_mark
            return (strength_class_,steel_mark_)
        except: raise

    def strength_led(self,strength_class:str):
        """ П0121_Приведение_Кл_прочности """
        try:
            result:int=0
            strength_class_int:int = 1000
            try: 
                strength_class_int=int(strength_class[1:])
            except: strength_class_int=1000

            if strength_class_int<100: result=65
            if strength_class_int<65: result=60
            if strength_class_int<60: result=55
            if strength_class_int<55: result=52
            if strength_class_int==55 or result==55: result=56
            return f"К{result}"
        except: raise

    def get_gmsps(self,steel_mark:str,product:str):
        """ П0130_Марка_стали_по_Гр_свойств (ГМСПС) """
        try:
            if pd.isna(steel_mark): steel_mark="+"
            result = self.scalar_in_guid("Группы марок стали по потребительским свойствам", f"`Марка стали`=='{steel_mark}'","Группа марок")
            if isinstance(steel_mark,int):
                print("steel is digit")
                result = self.scalar_in_guid("Группы марок стали по потребительским свойствам", f"`Марка стали`=={steel_mark}","Группа марок")
            if pd.isna(steel_mark) or str(steel_mark)=="-": result="ГУ"
            if pd.notna(result) and product=="ТРУБА Д" and result=="ГЮ": result="ГУ"
            return result
        except: raise

    def strength_group_by_steel(self,strength_class:str,steel_mark:str):
        """ П0111_Группа_по_классу_прочности_для_труб """
        try:
            result1 = self.scalar_in_guid("Группа по классу прочности для труб", f"`Марка стали`=='{strength_class}'","Группа прочности")
            result2 = self.scalar_in_guid("Группа по классу прочности для труб", f"`Марка стали`=='{steel_mark}'","Группа прочности")
            if pd.isna(result1) and pd.isna(result2): return "ПD"
            else:
                if pd.isna(result2) : return result1
                else: return result2
        except: raise
        
    def carving_type(self,carving:str):
        """ П0140_Резьба_типовая """
        try:
            carving_type = self.scalar_in_guid("Резьба",f"`Исх резьба`=='{carving}'","Результ Резьба")
            if pd.isna(carving) or pd.isna(carving_type): carving_type="БР"
            return carving_type
        except: raise